// rleunzip.c - RLE 解压缩程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>  // 用于 CreateDirectoryA
#include <direct.h>   // 用于 _mkdir，但为了 Windows API 的一致性，首选 CreateDirectoryA
#include "rle_common.h"

// 创建给定 entry_path 所需的目录结构。
// entry_path 是存档中的相对路径，例如 "dir1\\dir2\\file.txt" 或 "dir1\\dir2\\dir3\\"
// type 是条目的 EntryType。
int ensure_directory_structure_exists(const char *entry_path, EntryType type) {
    char path_to_create[MAX_FILENAME_LEN * 3]; // Increased buffer size
    char current_segment_path[MAX_FILENAME_LEN * 3]; // Increased buffer size
    const char *p_start, *p_end;

    strncpy(path_to_create, entry_path, sizeof(path_to_create) - 1);
    path_to_create[sizeof(path_to_create) - 1] = '\0';
    size_t path_to_create_len = strlen(path_to_create);

    if (type == RLE_FILE_TYPE) {
        char *last_sep = strrchr(path_to_create, '\\');
        char *last_fwd_sep = strrchr(path_to_create, '/');
        if (last_fwd_sep > last_sep) {
            last_sep = last_fwd_sep;
        }

        if (last_sep) {
            // It's a file within a directory (e.g., "dir/file.txt" or "dir\\file.txt")
            // We need to ensure "dir" exists.
            *last_sep = '\0'; // Truncate to get the directory path
        } else {
            // It's a file in the root of the archive (e.g., "file.txt")
            // No directory needs to be created for the file itself.
            // The current directory is assumed to exist.
            path_to_create[0] = '\0'; // Effectively makes path_to_create empty
        }
    } else if (type == RLE_DIRECTORY_TYPE) {
        // For directories like "somedir/" or "somedir\\", remove trailing slash if present
        // so that "somedir" is created.
        // path_to_create_len is already set from before this block
        if (path_to_create_len > 0 && 
            (path_to_create[path_to_create_len - 1] == '\\' || path_to_create[path_to_create_len - 1] == '/')) {
            // But don't remove from "C:\"
            if (! (path_to_create_len == 3 && path_to_create[1] == ':') ) {
                 path_to_create[path_to_create_len - 1] = '\0';
            }
        }
    }
    path_to_create_len = strlen(path_to_create); // Update length after potential modification
    if (path_to_create_len == 0) {
        return 0;
    }
    
    p_start = path_to_create;
    current_segment_path[0] = '\0';

    while (*p_start) {
        while (*p_start == '\\' || *p_start == '/') p_start++;
        if (!*p_start) break;

        p_end = strchr(p_start, '\\');
        if (!p_end) p_end = strchr(p_start, '/');

        size_t component_len;
        if (p_end) {
            component_len = p_end - p_start;
        } else {
            component_len = strlen(p_start);
        }

        if (component_len == 0) {
            if (p_end) { p_start = p_end + 1; continue; } 
            else break;
        }
        
        if (strlen(current_segment_path) > 0) {
            if (! (strlen(current_segment_path) == 2 && current_segment_path[1] == ':') ) {
                 strncat(current_segment_path, "\\", sizeof(current_segment_path) - strlen(current_segment_path) - 1);
            } else if (component_len > 0 && (p_start[0] != '\\' && p_start[0] != '/')) {
                 strncat(current_segment_path, "\\", sizeof(current_segment_path) - strlen(current_segment_path) - 1);
            }
        }
        
        char temp_component_buffer[MAX_FILENAME_LEN];
        if (component_len >= sizeof(temp_component_buffer)) {
            fprintf(stderr, "Error: Path component too long.\n");
            return -1;
        }
        strncpy(temp_component_buffer, p_start, component_len);
        temp_component_buffer[component_len] = '\0';
        
        if (strlen(current_segment_path) + strlen(temp_component_buffer) >= sizeof(current_segment_path)) {
            fprintf(stderr, "Error: Constructed path too long.\n");
            return -1;
        }
        strcat(current_segment_path, temp_component_buffer);

        if (strcmp(current_segment_path, ".") == 0) {
            // 无操作
        } else if (strlen(current_segment_path) == 2 && current_segment_path[1] == ':' && 
                   ((current_segment_path[0] >= 'a' && current_segment_path[0] <= 'z') || 
                    (current_segment_path[0] >= 'A' && current_segment_path[0] <= 'Z'))) {
            DWORD drive_attributes = GetFileAttributesA(current_segment_path);
            if (drive_attributes == INVALID_FILE_ATTRIBUTES || !(drive_attributes & FILE_ATTRIBUTE_DIRECTORY)) {
                 fprintf(stderr, "Error: Drive %s is not accessible or not a directory.\n", current_segment_path);
                 return -1;
            }
        } else {
            DWORD attributes = GetFileAttributesA(current_segment_path);
            if (attributes == INVALID_FILE_ATTRIBUTES) {
                if (!CreateDirectoryA(current_segment_path, NULL)) {
                    fprintf(stderr, "Error: Cannot create directory %s (Error code: %lu)\n", current_segment_path, GetLastError());
                    return -1;
                }
                printf("Created directory: %s\n", current_segment_path);
            } else if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
                fprintf(stderr, "Error: Path %s exists but is not a directory.\n", current_segment_path);
                return -1;
            }
        }

        if (p_end) {
            p_start = p_end + 1;
        } else {
            break; 
        }
    }
    return 0;
}


int main(int argc, char *argv[]) {
    const char *output_base_dir = "."; // Default to current directory
    if (argc != 2 && argc != 3) {
        fprintf(stderr, "Usage: rleunzip <input_file.rzp> [output_directory]\n");
        return 1;
    }

    const char *archive_filename = argv[1];
    if (argc == 3) {
        output_base_dir = argv[2];
    }
    FILE *archive_fp = fopen(archive_filename, "rb");
    if (!archive_fp) {
        perror("Error: Cannot open input archive file");
        return 1;
    }

    printf("rleunzip program started. Processing archive: %s\n", archive_filename);

    RzpEntryHeader header;
    int header_read_status;

    while ((header_read_status = read_entry_header(archive_fp, &header)) == 1) {
        printf("Read entry: Name='%s', Type=%s, RelativePath='%s', OriginalSize=%I64d, CompressedSize=%I64d\n",
               header.name,
               header.type == RLE_DIRECTORY_TYPE ? "Directory" : "File",
               header.relative_path,
               header.original_size,
               header.compressed_size);

        // 构建完整输出路径
        // header.relative_path 是存储在存档中的路径。
        // 我们将提取到 output_base_dir/header.relative_path
        char output_path[MAX_FILENAME_LEN * 3]; // Increased buffer size
        snprintf(output_path, sizeof(output_path), "%s\\%s", output_base_dir, header.relative_path);
        // Ensure the base output directory itself exists
        if (ensure_directory_structure_exists(output_base_dir, RLE_DIRECTORY_TYPE) != 0) {
            fprintf(stderr, "Failed to create base output directory %s\n", output_base_dir);
            fclose(archive_fp);
            return 1;
        }
        // 规范化本地系统的路径分隔符（Windows 使用 \，但 / 通常也有效）
        for (char *s = output_path; *s; ++s) {
            if (*s == '/') *s = '\\'; // 规范化为反斜杠以保持 Windows 的一致性
        }

        // 在尝试打开/创建文件或最终目录之前，确保目录结构存在
        if (ensure_directory_structure_exists(output_path, header.type) != 0) {
            fprintf(stderr, "Failed to create directory structure for %s\n", output_path);
            // 如果目录创建失败，则跳过此条目
            if (header.type == RLE_FILE_TYPE && header.compressed_size > 0) {
                if (fseek(archive_fp, header.compressed_size, SEEK_CUR) != 0) {
                    perror("Error seeking past file entry data after dir creation failure");
                    fclose(archive_fp);
                    return 1;
                }
            }
            continue; 
        }

        if (header.type == RLE_DIRECTORY_TYPE) {
            // ensure_directory_structure_exists 函数应该已经创建了目录。
            // 类似于 "somedir/" 的目录条目的 output_path 将是 "somedir\\".
            // 我们可以记录我们已经处理了它。
            printf("Processed directory entry (structure ensured): %s\n", output_path);
            // 目录条目本身没有数据可读，compressed_size 应为 0。
        } else if (header.type == RLE_FILE_TYPE) {
            FILE *output_fp = fopen(output_path, "wb");
            if (!output_fp) {
                fprintf(stderr, "Error: Cannot create output file %s\n", output_path);
                // 跳过存档文件中此条目的数据
                if (header.compressed_size > 0) {
                    if (fseek(archive_fp, header.compressed_size, SEEK_CUR) != 0) {
                        perror("Error seeking past file entry data after failed open");
                        fclose(archive_fp);
                        return 1;
                    }
                }
                continue;
            }

            printf("Decompressing file: %s (Compressed: %I64d, Original: %I64d)\n", 
                   output_path, header.compressed_size, header.original_size);
            
            if (header.compressed_size == 0 && header.original_size == 0) {
                // 空文件，无需解压缩，文件已创建为空。
                printf("File %s is empty. Created empty file.\n", output_path);
            } else if (header.compressed_size > 0) {
                if (rle_decompress(archive_fp, output_fp, header.compressed_size, header.original_size) != 0) {
                    fprintf(stderr, "Error: Decompression failed for file %s\n", output_path);
                    // 输出文件可能不完整/已损坏。关闭它。
                    fclose(output_fp);
                    // 可选择在此处删除 output_path
                    // 由于 rle_decompress 读取了压缩数据，存档流位置现在位于压缩数据之后。
                    // 因此，继续到下一个标头。
                    continue; 
                }
            } else if (header.compressed_size == 0 && header.original_size > 0) {
                 fprintf(stderr, "Warning: File %s has original size %I64d but compressed size 0. Creating empty file.\n", 
                        output_path, header.original_size);
                 // 文件通过 fopen(...,"wb") 创建为空
            }

            fclose(output_fp);
            printf("Successfully decompressed and wrote %s\n", output_path);
        } else {
            fprintf(stderr, "Warning: Unknown entry type %d for entry '%s'. Skipping.\n", header.type, header.name);
            if (header.compressed_size > 0) {
                 if (fseek(archive_fp, header.compressed_size, SEEK_CUR) != 0) {
                    perror("Error seeking past unknown entry data");
                    fclose(archive_fp);
                    return 1;
                }
            }
        }
    }

    if (header_read_status == -1) {
        perror("Error reading entry header from archive");
    } else if (feof(archive_fp)) {
        printf("Reached end of archive file.\n");
    }

    fclose(archive_fp);
    printf("rleunzip program finished.\n");
    return 0;
}